#![allow(non_snake_case)]
use crate::components::download_xlsx_file_with_url;
use crate::components::loading::Loading;
use crate::report::sub_report::{SubReport, SubHeaderSignal};
use crate::{components::server_error::ServerError, report::report_header::ReportHeader};
use either_of::Either;
use leptos::prelude::*;
use shq_common::prelude::DrawerPlacement;
use shq_common::prelude::{
    report::{DisplayCols, ReportDataResponse, ReportDefine},
    AuthType, ColumnCells, ExportReptParams, OrderItems, WhereCauses,
};
use shq_web_controls::{
    use_message, ColumnShows, ListTableServer, MessageOptions, MessageVariant, OrderFilterModel,
    OrderFliterList, TableFooter, TableState, TreeItems,
};
use std::collections::HashMap;
use thaw_utils::{class_list, ArcOneCallback, SignalWatch};
#[component]
pub fn ListReport(
    menu_id: u32,
    #[prop(into)] rept_name: StoredValue<String>,
    #[prop(into)] rept_define: Signal<ReportDefine>,
    #[prop(optional, into)] title: MaybeProp<String>,
    #[prop(optional, into)] params: MaybeProp<Vec<(String, String)>>,
    #[prop(optional, into)] extra_where: MaybeProp<String>,
) -> impl IntoView {
    let init_param_vals = rept_define.with_untracked(|rd| {
        rd.params
            .iter()
            .map(|p| {
                params.with_untracked(|ps| {
                    if let Some(ps) = ps {
                        if let Some(v) = ps.iter().find(|(k, _)| k == &p.name) {
                            (p.name.clone(), v.1.clone())
                        } else {
                            (p.name.clone(), p.default_value.to_string())
                        }
                    } else {
                        (p.name.clone(), p.default_value.to_string())
                    }
                })
            })
            .collect::<Vec<_>>()
    });
    let param_vals: RwSignal<Vec<(String, String)>> = RwSignal::new(init_param_vals);
    Effect::new(move |_| {
        params.get().map(|ps| {
            param_vals.update(|pvs| {
                for (pn, pv) in ps.iter() {
                    if let Some(v) = pvs.iter_mut().find(|(k, _)| k == pn) {
                        v.1 = pv.clone();
                    } else {
                        pvs.push((pn.clone(), pv.clone()));
                    }
                }
            });
        });
    });
    let group_index = RwSignal::new(0_u8);
    let inti_table_state = move || {
        rept_define.with_untracked(|rd| {
            let cols = rd.columns.as_slice();
            let excludeCols = rd.get_group_not_show_cols(group_index.get_untracked());
            TableState::from_column_cells(rept_name, cols, excludeCols.as_slice())
        })
    };
    let (table_state, _) = signal(inti_table_state());
    provide_context(table_state);
    let special_rows = rept_define.with_untracked(|rd| {
        rd.special_rows
            .as_ref()
            .cloned()
            .map(|sr| StoredValue::new(sr))
    });
    if let Some(special_rows) = special_rows {
        provide_context(special_rows);
    }
    let context_menus = rept_define.with_untracked(|rd| {
        if let Some(cms) = rd.context_menus.as_ref() {
            Some(StoredValue::new(cms.clone()))
        } else {
            None
        }
    });
    if let Some(context_menus) = context_menus {
        provide_context(context_menus);
    }
    let tree_items = rept_define.with_untracked(|rd| {
        let gidx = group_index.get_untracked();
        let not_show_cols: Vec<String> = rd
            .get_group_not_show_cols(gidx)
            .into_iter()
            .map(|v| v.to_owned())
            .collect();
        StoredValue::new(TreeItems::from_column_cells(&rd.columns, not_show_cols))
    });
    _ = group_index.watch(move |gidx| {
        table_state.with_untracked(|ts| {
            rept_define.with_untracked(|rd| {
                let show_cols = rd.get_group_show_cols(*gidx);
                show_cols.iter().for_each(|col| {
                    ts.add_display(col);
                });
                let not_show_cols = rd.get_group_not_show_cols(*gidx);
                not_show_cols.iter().for_each(|col| {
                    ts.del_display(col);
                });
                let not_shows = not_show_cols
                    .into_iter()
                    .map(|v| v.to_owned())
                    .collect::<Vec<_>>();
                tree_items.with_value(|ts| {
                    ts.set_not_shows(not_shows);
                });
            });
        });
    });
    let page = table_state.with_untracked(|ts| ts.page());
    let page_size = table_state.with_untracked(|ts| ts.page_size());
    let display_sigal = table_state.with_untracked(|ts| ts.display());
    let header_cells = Memo::new(move |_| {
        display_sigal.with(|ds| {
            let display_cols: Vec<&str> = ds.iter().map(|s| s.as_str()).collect();
            rept_define.with_untracked(|rd| {
                ColumnCells::from_columncells(rd.columns.clone(), &display_cols)
            })
        })
    });
    let orders = Memo::new(move |_| {
        table_state.with_untracked(|ts| {
            let orders = ts.get_orders();
            if orders.is_empty() {
                None
            } else {
                Some(OrderItems::from(orders))
            }
        })
    });
    let filters = Memo::new(move |_| {
        table_state.with_untracked(|ts| {
            let filters = ts.get_filters();
            if filters.is_empty() {
                None
            } else {
                Some(WhereCauses::from(filters))
            }
        })
    });
    let mess = use_message();
    let json_vals = Resource::new(
        move || {
            (
                param_vals.get(),
                filters.get(),
                orders.get(),
                display_sigal.get(),
                group_index.get(),
                extra_where.get(),
                page.get(),
                page_size.get(),
            )
        },
        move |(
            param_vals,
            filters,
            orders,
            display_sigal,
            group_index,
            extra_where,
            page,
            page_size,
        )| async move {
            let jval_res = get_rept_data(
                menu_id,
                rept_name.get_value(),
                param_vals,
                filters.map(|s| serde_json::to_string(&s).unwrap()),
                orders.map(|s| serde_json::to_string(&s).unwrap()),
                display_sigal,
                group_index,
                extra_where,
                page as u32,
                page_size as u16,
            )
            .await;
            jval_res
        },
    );
    Effect::new(move |_| match json_vals.get() {
        None => {}
        Some(Ok(res)) => {
            table_state.with_untracked(|ts| {
                let llen = res.data.len();
                ts.total().set(res.total as usize);
                ts.list_data().set(res.data);
                if let Some(cms) = context_menus {
                    if llen > 0
                        && ts.context_action_index().get_untracked().is_none()
                        && ts.selected_indexs().with_untracked(|idxs| idxs.is_empty())
                    {
                        let menu_idx = cms.with_value(|cms| cms.get_row_context_menu());
                        if let Some(menu_idx) = menu_idx {
                            ts.selected_indexs().set(vec![0]);
                            ts.context_action_index().set(Some(menu_idx));
                        }
                    }
                }
            });
        }
        Some(Err(err)) => {
            mess.create(
                err.to_string(),
                MessageVariant::Error,
                MessageOptions::default(),
            );
        }
    });
    let export = Action::new(move |&one_sheet_on_file: &bool| {
        let rept_name = rept_name.get_value();
        let filters = filters.with(|f| f.as_ref().map(|s| serde_json::to_string(s).unwrap()));
        let orders = orders.with(|o| o.as_ref().map(|s| serde_json::to_string(s).unwrap()));
        let pvals = param_vals.with(|ps| {
            let pvals = ps.iter().map(|(_, v)| v.as_str()).collect::<Vec<_>>();
            pvals.join("-")
        });
        let extra_where = extra_where.get();
        let title = rept_define.with_untracked(|rd| format!("{}{}", rd.title.as_str(), pvals));
        let display_cols = display_sigal.get();
        async move {
            let eqp = ExportReptParams {
                rept_name,
                filters,
                orders,
                params: param_vals.get(),
                display_cols: DisplayCols::Part(display_cols),
                group_index: group_index.get(),
                drill_down_requ: vec![],
                extra_where,
                user_id: 0,
                perm_id: menu_id,
                one_sheet_on_file,
                rebuild: false,
            };
            let params = serde_qs::to_string(&eqp).unwrap();
            let ext = if one_sheet_on_file { "zip" } else { "xlsx" };
            if let Err(err) =
                download_xlsx_file_with_url(&title, ext, "/export_rept_xlsx", Some(params))
            {
                mess.create(err, MessageVariant::Error, MessageOptions::default());
            }
        }
    });
    let opts_map_vals = LocalResource::new(move || {
        get_rept_options_map(menu_id, rept_name.get_value(), param_vals.get())
    });
    let curr_cell = table_state.with_untracked(|ts| ts.curr_cell());
    let sel_col = table_state.with_untracked(|ts| ts.selected_col());
    let context_action_index = table_state.with_untracked(|ts| ts.context_action_index());
    let maximize_sub_window = table_state.with_untracked(|ts| ts.maximize_sub_window());
    let total = table_state.with_untracked(|ts| ts.total());
    let menu_item = Memo::new(move |_| {
        let context_action_index = context_action_index.get();
        if let Some(context_action_index) = context_action_index {
            let cms = context_menus
                .map(|cms| {
                    cms.with_value(|cms| cms.get_context_menu_item(context_action_index).cloned())
                })
                .unwrap_or(None);
            cms
        } else {
            None
        }
    });
    let sub_style = MaybeProp::derive(move || {
        menu_item.with(|menu_item| {
            if menu_item.is_none() {
                return None;
            }
            let menu_item = menu_item.as_ref().unwrap();
            let grid = if maximize_sub_window.get() {
                12.0
            } else {
                menu_item.drawer.grid as f64
            };
            let placement = menu_item.drawer.placement.clone();
            let span_style =
                if placement == DrawerPlacement::Top || placement == DrawerPlacement::Bottom {
                    format!("height:{}%", grid * 100.0 / 12.0)
                } else {
                    format!("width:{}%", grid * 100.0 / 12.0)
                };
            Some(span_style)
        })
    });
    let main_style = Memo::new(move |_| {
        menu_item.with(|menu_item| {
            if menu_item.is_none() {
                return ("".to_owned(), "".to_owned());
            }
            let menu_item = menu_item.as_ref().unwrap();
            let grid = if maximize_sub_window.get() {
                12.0
            } else {
                12.0 - menu_item.drawer.grid as f64
            };
            let placement = menu_item.drawer.placement.clone();
            let span_style =
                if placement == DrawerPlacement::Top || placement == DrawerPlacement::Bottom {
                    format!("height:{}%", grid * 100.0 / 12.0)
                } else {
                    format!("width:{}%", grid * 100.0 / 12.0)
                };
            let main_class = match placement {
                DrawerPlacement::Top => "flex flex-col-reverse",
                DrawerPlacement::Right => "flex flex-row",
                DrawerPlacement::Bottom => "flex flex-col",
                DrawerPlacement::Left => "flex flex-row-reverse",
            };
            (span_style, main_class.to_owned())
        })
    });
    let has_export_sheet_col = rept_define.with_untracked(|rd| rd.has_export_sheet_col);
    let row_data = Memo::new(move |_| table_state.with_untracked(|ts| ts.get_curr_row()));
    let cur_col_index = MaybeProp::derive(move || curr_cell.with(|c| c.map(|c| c.1)));
    let sub_header_signal = SubHeaderSignal {
        context_action_index,
        maximize_sub_window,
    };
    view! {
        <div class=class_list!["flex-1 w-full h-full gap-2 overflow-hidden",move||main_style.with(|s|s.1.clone())]>
            <div class="flex-1 flex flex-col w-full h-full space-y-1 overflow-hidden"
                style=move||main_style.with(|s|s.0.clone())
                class:hidden=move||maximize_sub_window.get()>
                <ReportHeader title rept_define param_vals group_index/>
                <div class="flex-1 bg-base-100 overflow-auto relative h-full w-full">
                    <ListTableServer header_cells multi_sel=false cell_border=true/>
                </div>
                <TableFooter total=total has_export_sheet_col export_click=ArcOneCallback::new(move |one_sheet_on_file:bool| {
                    export.dispatch(one_sheet_on_file);
                }) />
            </div>
            <SubReport menu_id header_cells param_vals cur_col_index menu_item sub_header_signal sel_col=sel_col.read_only() sub_style row_data/>
        </div>
        <Transition fallback=|| view! { <Loading/> }>
            <Show
                    when= move || opts_map_vals.get().as_deref().is_some()
                    fallback=|| view! { <p>"Loading..."</p> }
                >
                    {
                        match opts_map_vals.get().as_deref().cloned().unwrap() {
                            Ok(resp) => {
                                let opts_map = Memo::new(move |_|{
                                    let opts_map: HashMap<String, StoredValue<TreeItems>> =
                                    resp
                                        .iter()
                                        .map(|(k, v)| (k.to_owned(), StoredValue::new(v.into())))
                                        .collect::<HashMap<_, _>>();
                                    opts_map
                                });
                                Either::Left(view!{
                                    <OrderFilterModel header_cells opts_map=opts_map/>
                                })
                            }
                            Err(err) => Either::Right(view! {
                                <div class="flex-1 bg-base-100 overflow-auto ">
                                    <ServerError err_msg=err.to_string().into()/>
                                </div>
                            }),
                        }
                    }
            </Show>
        </Transition>
        <ColumnShows tree_items/>
        <OrderFliterList tree_items/>
    }
    .into_any()
}

#[server(GetReptData, "/api/protect")]
pub async fn get_rept_data(
    menu_id: u32,
    rept_name: String,
    #[server(default)] params: Vec<(String, String)>,
    where_causes: Option<String>,
    orders: Option<String>,
    display_cols: Vec<String>,
    group_index: u8,
    extra_where: Option<String>,
    page: u32,
    size: u16,
) -> Result<ReportDataResponse, ServerFnError> {
    use crate::auth::get_user_context_with_auth;
    let user = get_user_context_with_auth(menu_id, AuthType::Select).await?;
    let params: Vec<(&str, &str)> = params
        .iter()
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();
    let fs: Option<WhereCauses> = where_causes.map(|f| serde_json::from_str(&f).unwrap());
    let os: Option<OrderItems> = orders.map(|o| serde_json::from_str(&o).unwrap());
    let rept = shq_report::ReportService::new(&rept_name, &params, user.user_id, menu_id).await?;
    let display_cols = if display_cols.is_empty() {
        DisplayCols::Default
    } else {
        DisplayCols::Part(display_cols)
    };
    let result = rept
        .get_data_list(
            fs,
            os,
            display_cols,
            group_index,
            vec![],
            extra_where,
            page,
            size,
        )
        .await;
    match result {
        Ok(datas) => Ok(datas),
        Err(e) => {
            log::error!("get_rept_data error:{}", e);
            Err(ServerFnError::ServerError(e.to_string()))
        }
    }
}
#[server(GetReptOptionsMap, "/api/protect")]
pub async fn get_rept_options_map(
    menu_id: u32,
    rept_name: String,
    #[server(default)] params: Vec<(String, String)>,
) -> Result<HashMap<String, Vec<String>>, ServerFnError> {
    use crate::auth::get_user_context_with_auth;
    let user = get_user_context_with_auth(menu_id, AuthType::Select).await?;
    let params: Vec<(&str, &str)> = params
        .iter()
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();
    let rept = shq_report::ReportService::new(&rept_name, &params, user.user_id, menu_id).await?;
    let result = rept.get_options_map().await;
    match result {
        Ok(datas) => {
            Ok(datas)
        }
        Err(e) => {
            log::error!("get_rept_options_map error:{}", e);
            Err(ServerFnError::ServerError(e.to_string()))
        }
    }
}
